﻿using System;
using System.Collections;
using System.IO;
using Cecil.FlowAnalysis;
using Cecil.FlowAnalysis.ActionFlow;
using Cecil.FlowAnalysis.CodeStructure;
using Cecil.FlowAnalysis.ControlFlow;
using Cecil.FlowAnalysis.Utilities;
using Microsoft.VisualStudio.TestTools.UnitTesting;
using Mono.Cecil;
using Mono.Cecil.Cil;

namespace OpenReflect.Test.BuilderTests
{
	/// <summary>
	/// Summary description for SandBox
	/// </summary>
	[TestClass]
	public class SandBox
	{
		/// <summary>
		///Gets or sets the test context which provides
		///information about and functionality for the current test run.
		///</summary>
		public TestContext TestContext { get; set; }

		private TypeDefinition TDef
		{
			get { return Definition.MainModule.Types["OpenReflect.Test.TestBinaries.TestClass1"]; }
		}

		private AssemblyDefinition Definition { get; set; }

		#region Additional test attributes

		// 
		//You can use the following additional attributes as you write your tests:
		//
		//Use ClassInitialize to run code before running the first test in the class
		//[ClassInitialize()]
		//public static void MyClassInitialize(TestContext testContext)
		//{
		//}
		//
		//Use ClassCleanup to run code after all tests in a class have run
		//[ClassCleanup()]
		//public static void MyClassCleanup()
		//{
		//}
		//
		//Use TestInitialize to run code before running each test
		//[TestInitialize()]
		//public void MyTestInitialize()
		//{
		//}
		//
		//Use TestCleanup to run code after each test has run
		//[TestCleanup()]
		//public void MyTestCleanup()
		//{
		//}
		//

		#endregion

		[TestInitialize]
		public void MyTestInitialize()
		{
			Definition =
				AssemblyFactory.GetAssembly(
					@"OpenReflect.Test.TestBinaries.dll");
		}

		[TestMethod]
		public void TestMethod1()
		{
			MethodDefinition method = TestHelper.CreateMethodDefinition(TDef, "NormalAddMethod");
			//ActionFlowGraph afg = FlowGraphFactory.CreateActionFlowGraph(method);
			ControlFlowGraph cfg = FlowGraphFactory.CreateControlFlowGraph(method);
			Console.WriteLine(ToString(cfg));
			//ActionFlowGraph afg = FlowGraphFactory.CreateActionFlowGraph(cfg);
			//Console.WriteLine(ToString(afg));
		}

		public static string ToString(ControlFlowGraph cfg)
		{
			var writer = new StringWriter();
			FormatControlFlowGraph(writer, cfg);
			return writer.ToString();
		}

		public static void FormatControlFlowGraph(TextWriter writer, ControlFlowGraph cfg)
		{
			int id = 1;
			foreach (InstructionBlock block in cfg.Blocks)
			{
				writer.WriteLine("block {0}:", id);
				writer.WriteLine("\tbody:");
				foreach (Instruction instruction in block)
				{
					writer.Write("\t\t");
					Formatter.WriteInstruction(writer, instruction);
					writer.WriteLine();
				}
				InstructionBlock[] successors = block.Successors;
				if (successors.Length > 0)
				{
					writer.WriteLine("\tsuccessors:");
					foreach (InstructionBlock successor in successors)
					{
						writer.WriteLine("\t\tblock {0}", GetBlockId(cfg, successor));
					}
				}

				++id;
			}
		}

		private static int GetBlockId(ControlFlowGraph cfg, InstructionBlock block)
		{
			return ((IList) cfg.Blocks).IndexOf(block) + 1;
		}

		public static string ToString(ActionFlowGraph afg)
		{
			var writer = new StringWriter();
			new ActionFlowGraphPrinter(writer).Print(afg);
			return writer.ToString();
		}

		#region Nested type: ActionFlowGraphPrinter

		private class ActionFlowGraphPrinter
		{
			private readonly ExpressionPrinter _expressionPrinter;
			private readonly TextWriter _writer;

			public ActionFlowGraphPrinter(TextWriter writer)
			{
				_writer = writer;
				_expressionPrinter = new ExpressionPrinter(writer);
			}

			public void Print(ActionFlowGraph afg)
			{
				int i = 1;
				foreach (ActionBlock block in afg.Blocks)
				{
					if (afg.IsBranchTarget(block))
					{
						WriteLabel(i);
					}
					switch (block.ActionType)
					{
						case ActionType.Return:
							WriteReturn((ReturnActionBlock) block);
							break;

						case ActionType.Branch:
							WriteBranch(afg, (BranchActionBlock) block);
							break;

						case ActionType.ConditionalBranch:
							WriteConditionalBranch(afg, (ConditionalBranchActionBlock) block);
							break;

						case ActionType.Assign:
							WriteAssign((AssignActionBlock) block);
							break;

						case ActionType.Invoke:
							WriteInvoke((InvokeActionBlock) block);
							break;

						default:
							throw new InvalidOperationException();
					}
					_writer.WriteLine();
					++i;
				}
			}

			private void WriteLabel(int index)
			{
				_writer.Write("block{0}: ", index);
			}

			private void WriteConditionalBranch(ActionFlowGraph afg, ConditionalBranchActionBlock block)
			{
				_writer.Write("if ");
				WriteExpression(block.Condition);
				_writer.Write(' ');
				WriteGoto(afg, block.Then);
			}

			private void WriteBranch(ActionFlowGraph afg, BranchActionBlock block)
			{
				WriteGoto(afg, block.Target);
			}

			private void WriteGoto(ActionFlowGraph afg, ActionBlock target)
			{
				_writer.Write("goto block{0}", afg.Blocks.IndexOf(target) + 1);
			}

			private void WriteAssign(AssignActionBlock block)
			{
				WriteExpression(block.AssignExpression);
			}

			private void WriteReturn(ReturnActionBlock block)
			{
				_writer.Write("return");
				if (null != block.Expression)
				{
					_writer.Write(" ");
					WriteExpression(block.Expression);
				}
			}

			private void WriteInvoke(InvokeActionBlock block)
			{
				WriteExpression(block.Expression);
			}

			private void WriteExpression(Expression expression)
			{
				_expressionPrinter.Visit(expression);
			}
		}

		#endregion
	}
}